<template>
  <el-upload
    class="upload-demo"
    drag
    :limit="limit"
    ref="fileUpload"
    action="/"
    :http-request="handleHttpRequest"
    :on-remove="handleRemoveFile"
    multiple
  >
    <i class="el-icon-upload"></i>
    <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
  </el-upload>
</template>

<script>
import { getPreSignUploadUrl, merge, upload } from '@/api/system/file'
import { md5 } from '@/utils/md5'
import axios from 'axios'
import Queue from 'promise-queue-plus'

export default {
  name: 'minioUpload',
  props: {
    limit: {
      type: Number,
      default: 5
    }
  },
  data() {
    return {
      fileUploadChunkQueue: {}
    }
  },
  methods: {
    // 上传文件
    async handleUpload(file, taskRecord, options) {
      let lastUploadedSize = 0 // 上次断点续传时上传的总大小
      let uploadedSize = 0 // 已上传的大小
      const totalSize = file.size || 0 // 文件总大小
      let startMs = new Date().getTime() // 开始上传的时间
      const { exitPartList, chunkSize, chunkNum, fileMd5 } = taskRecord

      /**
       * 获取从开始上传到现在的平均速度（byte/s）
       * @returns {number}
       */
      const getSpeed = () => {
        const intervalSize = uploadedSize - lastUploadedSize
        const nowMs = new Date().getTime()
        const intervalTime = (nowMs - startMs) / 1000
        return intervalSize / intervalTime
      }

      /**
       * 上传分片
       * @param partNumber 分片序号
       * @returns {Promise<{uploadedSize, partNumber}>}
       */
      const uploadNext = async(partNumber) => {
        const start = Number(chunkSize) * (partNumber - 1)
        const end = start + Number(chunkSize)
        const blob = file.slice(start, end)
        let preSignUrl = await getPreSignUploadUrl(fileMd5, partNumber)
        if (preSignUrl.data) {
          await axios.request({
            url: preSignUrl.data,
            method: 'PUT',
            data: blob,
            headers: {
              'Content-Type': 'application/octet-stream'
            }
          })
          return Promise.resolve({
            partNumber: partNumber,
            uploadedSize: blob.size
          })
        }
        return Promise.reject(`分片${partNumber}， 获取上传地址失败`)
      }

      /**
       * 更新上传进度
       * @param increment 为已上传的进度增加的字节量
       */
      const updateProcess = (increment) => {
        increment = Number(increment)
        const { onProgress } = options
        let factor = 1000 // 每次增加1000 byte
        let from = 0
        while (from <= increment) {
          from += factor
          uploadedSize += factor
          const percent = Math.min(((uploadedSize / totalSize) * 100).toFixed(2), 100)
          onProgress({ percent: Number(percent) })
        }
        const speed = getSpeed()
        const remainingTime = speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : '未知'
        console.log('剩余大小：', (totalSize - uploadedSize) / 1024 / 1024, 'mb')
        console.log('当前速度：', (speed / 1024 / 1024).toFixed(2), 'mbps')
        console.log('预计完成：', remainingTime)
      }

      return new Promise((resolve, reject) => {
        const failArr = []
        const queue = Queue(5, {
          retry: 3,
          retryIsJump: false,
          workReject: function(reason) {
            failArr.push(reason)
          },
          queueEnd() {
            if (failArr.length === 0) {
              resolve(failArr)
            } else {
              reject(failArr)
            }
          }
        })
        this.fileUploadChunkQueue[file.uid] = queue
        for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {
          const exitPart = (exitPartList || []).find((exitPart) => exitPart.partNumber == partNumber)
          if (exitPart) {
            lastUploadedSize += Number(exitPart.size)
            updateProcess(exitPart.size)
          } else {
            queue.push(() => {
              return uploadNext(partNumber).then(res => {
                updateProcess(res.uploadedSize)
              })
            })
          }
        }
        if (queue.getLength() == 0) {
          resolve(failArr)
          return
        }
        queue.start()
      })
    },

    // 自定义上传
    async handleHttpRequest(options) {
      const file = options.file
      const fileMd5 = await md5(file)
      let resp = await upload({ fileMd5, fileName: file.name, fileSize: file.size })
      const { isFinish, path, taskRecord } = resp.data
      if (isFinish) {
        this.$modal.notifySuccess(`文件${file.name}上传成功`)
        this.$emit('input', path)
        return path
      } else {
        const errorList = await this.handleUpload(file, taskRecord, options)
        if (errorList.length > 0) {
          this.$modal.notifyError('部分分片上传失败，请尝试重新上传文件')
          return
        }
        await merge(fileMd5)
        this.$modal.notifySuccess(`文件${file.name}上传成功`)
        this.$emit('input', path)
        return path
      }
    },
    handleRemoveFile(uploadFile) {
      const queueObject = this.fileUploadChunkQueue[uploadFile.uid]
      if (queueObject) {
        queueObject.stop()
        this.fileUploadChunkQueue[uploadFile.uid] = undefined
      }
    }
  }
}
</script>

<style scoped>
</style>
